package xyz.lilei23.framework.core;

import xyz.lilei23.framework.aop.ProxyFactory;
import xyz.lilei23.framework.bean.GenericBeanDefinition;
import xyz.lilei23.framework.transaction.TransactionManager;
import xyz.lilei23.framework.utils.CollectionUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static java.util.Locale.ENGLISH;

/**
 * 核心工厂
 * @author jack
 * @date 31/07/2021 16:20
 * @version 1.0
 **/
public class BeanFactory {


    private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(16);

    private final ConcurrentHashMap<String, GenericBeanDefinition> beanDefinitions = new ConcurrentHashMap<>(16);

    private final ConcurrentHashMap<Class<?>, Set<String>> allBeanNamesByType = new ConcurrentHashMap<>(16);

    private final List<BeanPostProcessor> beanPostProcessors = new CopyOnWriteArrayList<>();

    private  Boolean enableTransaction = false;

    private final ThreadLocal<Object> prototypesCurrentlyInCreation = new ThreadLocal<>();

    /**
     * 初始化时注册核心实例
     */
    public BeanFactory(ApplicationContext applicationContext) {
        coreRegister(applicationContext);
    }

    private void coreRegister(ApplicationContext applicationContext) {
        registerApplicationContext(applicationContext);
        registerBeanFactory();
        registerProxyFactory();
    }

    private void registerApplicationContext(ApplicationContext applicationContext) {
        String applicationContextId = "applicationContext";
        singletonObjects.put(applicationContextId, applicationContext);
        setDefaultBeanDefinition(applicationContextId);
        allBeanNamesByType.computeIfAbsent(ApplicationContext.class, k -> new HashSet<>());
        Set<String> names = allBeanNamesByType.get(ApplicationContext.class);
        names.add(applicationContextId);
        allBeanNamesByType.put(ApplicationContext.class, names);
    }


    private void registerBeanFactory() {
        String beanFactoryId = "beanFactory";
        singletonObjects.put(beanFactoryId, this);
        setDefaultBeanDefinition(beanFactoryId);
        allBeanNamesByType.computeIfAbsent(this.getClass(), k -> new HashSet<>());
        Set<String> names = allBeanNamesByType.get(this.getClass());
        names.add(beanFactoryId);
        allBeanNamesByType.put(this.getClass(), names);
    }

    private void registerProxyFactory() {
        String proxyFactoryId = "proxyFactory";
        singletonObjects.put(proxyFactoryId, new ProxyFactory());
        setDefaultBeanDefinition(proxyFactoryId);
        allBeanNamesByType.computeIfAbsent(ProxyFactory.class, k -> new HashSet<>());
        Set<String> proxyNames = allBeanNamesByType.get(ProxyFactory.class);
        proxyNames.add(proxyFactoryId);
        allBeanNamesByType.put(ProxyFactory.class, proxyNames);
    }

    private void setDefaultBeanDefinition(String applicationContextId) {
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setId(applicationContextId);
        beanDefinitions.put(applicationContextId, genericBeanDefinition);
    }


    public Object getBean(String beanName) {
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        }
        GenericBeanDefinition genericBeanDefinition = beanDefinitions.get(beanName);
        if (genericBeanDefinition == null) {
            throw new RuntimeException("当前不存在该bean: " + beanName);
        }
        // 多例的注入无值情况
        return createBean(genericBeanDefinition);
    }


    public Object getBean(Class<?> clazz) {
        return getBean(clazz, null);
    }

    public Object getBean(Class<?> clazz, String beanName) {
        Set<String> beanNames = getBeanNamesByType(clazz);
        if (beanNames == null || beanNames.size() == 0) {
            return null;
        }
        if (beanName == null && beanNames.size() > 1) {
            throw new RuntimeException("获取bean异常, 当前存在多值:" + beanNames.size());
        }
        if (beanName == null) {
            return getBean(beanNames.stream().findFirst().orElse(null));
        }
        if (beanNames.contains(beanName)) {
            return getBean(beanName);
        }
        return null;
    }

    public Object createBean(GenericBeanDefinition bd) {
        if (bd.isSingle() && singletonObjects.containsKey(bd.getId())) {
            return singletonObjects.get(bd.getId());
        }
        if (isPrototypeCurrentlyInCreation(bd.getId())) {
            throw new RuntimeException("不支持循环依赖");
        }
        beforeCreation(bd.getId());
        Object bean = doCreateBean(bd);
        afterCreation(bd.getId());
        return bean;
    }

    public void setBeanNameByType(GenericBeanDefinition bd) {
        allBeanNamesByType.computeIfAbsent(bd.getBeanClass(), k -> new HashSet<>());
        Set<String> names = allBeanNamesByType.get(bd.getBeanClass());
        names.add(bd.getId());
        allBeanNamesByType.put(bd.getBeanClass(), names);
    }

    private Object doCreateBean(GenericBeanDefinition bd) {
        Object bean;
        try {
            // 获取bean实例
            bean = getBeanInstance(bd);
            // 加入后置处理器处理
            bean = applyBeanBeforePostProcessors(bean, bd.getId());
            bean = applyBeanAfterPostProcessors(bean, bd.getId());
            allBeanNamesByType.get(bd.getBeanClass()).add(bd.getId());
            singletonObjects.putIfAbsent(bd.getId(), bean);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("bean创建异常");
        }
        return bean;
    }

    private Object getBeanInstance(GenericBeanDefinition bd) throws InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException, NoSuchMethodException {
        Object bean;
        List<GenericBeanDefinition.Arg> args = bd.getArgs();
        if (args.isEmpty()) {
            bean = bd.getBeanClass().newInstance();
        } else {
            Class<?>[] classes = new Class[args.size()];
            Object[] argObjects = new Object[args.size()];
            AtomicInteger inx = new AtomicInteger();
            args.forEach(arg -> {
                if (arg.isRef()) {
                    GenericBeanDefinition definition = beanDefinitions.get(arg.getArg());
                    if (definition == null) {
                        throw new RuntimeException("该属性没有bean定义:" + arg.getArg());
                    }
                    argObjects[inx.get()] = createBean(definition);
                } else {
                    try {
                        argObjects[inx.get()] = arg.getType().newInstance();
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException("bean创建异常:"+arg.getArg());
                    }
                }
                classes[inx.getAndIncrement()] = arg.getType();
            });
            bean = bd.getBeanClass().getConstructor(classes).newInstance(argObjects);
        }
        return bean;
    }

    private Object applyBeanAfterPostProcessors(Object bean, String beanId) {
        if (BeanPostProcessor.class.isAssignableFrom(bean.getClass())){
            return bean;
        }
        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            bean = beanPostProcessor.postProcessAfterInitialization(bean, beanId);
        }
        return bean;
    }

    private Object applyBeanBeforePostProcessors(Object bean, String beanId) {
        if (BeanPostProcessor.class.isAssignableFrom(bean.getClass())){
            return bean;
        }
        for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
            bean = beanPostProcessor.postProcessBeforeInitialization(bean, beanId);
        }
        return bean;
    }

    public void beanDefinitionProcess(List<GenericBeanDefinition> genericBeanDefinitionList) {
        genericBeanDefinitionList.forEach(beanDefinition -> beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition));
        genericBeanDefinitionList.forEach(
                beanDefinition -> {
                    if (!beanDefinition.isLazyInit() && beanDefinition.isSingle()) {
                        createBean(beanDefinition);
                    }
                }
        );
    }

    public List<BeanPostProcessor> getBeanPostProcessors() {
        return beanPostProcessors;
    }


    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }

    public static String capitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
    }

    protected void beforeCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        if (curVal == null) {
            this.prototypesCurrentlyInCreation.set(beanName);
        } else if (curVal instanceof String) {
            Set<String> beanNameSet = new HashSet<>(2);
            beanNameSet.add((String) curVal);
            beanNameSet.add(beanName);
            this.prototypesCurrentlyInCreation.set(beanNameSet);
        } else {
            Set<String> beanNameSet = (Set<String>) curVal;
            beanNameSet.add(beanName);
        }
    }

    protected void afterCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        if (curVal instanceof String) {
            this.prototypesCurrentlyInCreation.remove();
        } else if (curVal instanceof Set) {
            Set<String> beanNameSet = (Set<String>) curVal;
            beanNameSet.remove(beanName);
            if (beanNameSet.isEmpty()) {
                this.prototypesCurrentlyInCreation.remove();
            }
        }
    }

    public String[] getBeanNamesForType(Class<?> beanPostProcessorClass) {
        return Optional.ofNullable(getBeanNamesByType(beanPostProcessorClass)).orElse(new HashSet<>(0)).toArray(new String[0]);
    }

    private Set<String> getBeanNamesByType(Class<?> beanPostProcessorClass) {
        return allBeanNamesByType.keySet().stream().filter(beanPostProcessorClass::isAssignableFrom).flatMap(clazz -> allBeanNamesByType.get(clazz).stream()).collect(Collectors.toSet());
    }

    public boolean isTypeMatch(String ppName, Class<?> clazz) {
        GenericBeanDefinition genericBeanDefinition = beanDefinitions.get(ppName);
        return clazz.isAssignableFrom(genericBeanDefinition.getBeanClass());
    }

    public void addBeanPostProcessor(BeanPostProcessor postProcessor) {
        beanPostProcessors.add(postProcessor);
    }

    public void addBeanDefinition(GenericBeanDefinition genericBeanDefinition) {
        beanDefinitions.put(genericBeanDefinition.getId(), genericBeanDefinition);
        setBeanNameByType(genericBeanDefinition);
    }

    public boolean isEnableTransaction() {
        return enableTransaction;
    }

    public void enableTransaction() {
        enableTransaction = !CollectionUtils.isEmpty(getBeanNamesByType(TransactionManager.class));
    }

    public BeanDefinition getBeanDefinition(String beanName){
        return beanDefinitions.get(beanName);
    }
}
